home *** CD-ROM | disk | FTP | other *** search
/ Programmer Power Tools / Programmer Power Tools.iso / turbopas / tutorpas.arc / TUTOR7.DOC < prev    next >
Encoding:
Text File  |  1988-11-07  |  79.4 KB  |  2,595 lines

  1. O
  2. PA A
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  
  13.  
  14.  
  15.  
  16.  
  17.  
  18.  
  19.  
  20.  
  21.  
  22.  
  23.  
  24.  
  25.  
  26.  
  27.  
  28.  
  29.                             LET'S BUILD A COMPILER!
  30.  
  31.                                        By
  32.  
  33.                             Jack W. Crenshaw, Ph.D.
  34.  
  35.                                 7 November 1988
  36.  
  37.  
  38.                            Part VII: LEXICAL SCANNING
  39.  
  40.  
  41.  
  42.  
  43.  
  44.  
  45.  
  46.  
  47.  
  48.  
  49.  
  50.  
  51.  
  52.  
  53.  
  54.  
  55.  
  56.  
  57.  
  58.  
  59.  
  60.  
  61.  
  62.  
  63.  
  64.  
  65.  
  66.  
  67.  
  68.  
  69. PA A
  70.  
  71.  
  72.  
  73.  
  74.  
  75.        *****************************************************************
  76.        *                                                               *
  77.        *                        COPYRIGHT NOTICE                       *
  78.        *                                                               *
  79.        *   Copyright (C) 1988 Jack W. Crenshaw. All rights reserved.   *
  80.        *                                                               *
  81.        *****************************************************************
  82.  
  83.  
  84.        INTRODUCTION
  85.  
  86.        In the last installment, I left you with a  compiler  that  would
  87.        ALMOST  work,  except  that  we  were  still  limited to  single-
  88.        character tokens.  The purpose of  this  session is to get rid of
  89.        that restriction, once and for all.  This means that we must deal
  90.        with the concept of the lexical scanner.
  91.  
  92.        Maybe I should mention why we  need  a lexical scanner at all ...
  93.        after all, we've been able to manage all right  without  one,  up
  94.        till now, even when we provided for multi-character tokens.
  95.  
  96.        The ONLY reason, really, has to do with keywords.  It's a fact of
  97.        computer life that the syntax for a keyword has the same  form as
  98.        that  for  any  other identifier.  We can't tell until we get the
  99.        complete word whether or not it  IS  a keyword.  For example, the
  100.        variable IFILE and the keyword IF look just alike, until  you get
  101.        to the third character.  In the examples to date, we  were always
  102.        able to make  a  decision  based  upon the first character of the
  103.        token, but that's  no  longer possible when keywords are present.
  104.        We  need to know that a given string is a keyword BEFORE we begin
  105.        to process it.  And that's why we need a scanner.
  106.  
  107.        In the last session, I also promised that  we  would  be  able to
  108.        provide for normal tokens  without  making  wholesale  changes to
  109.        what we have  already done.  I didn't lie ... we can, as you will
  110.        see later.  But every time I set out to install these elements of
  111.        the software into  the  parser  we  have already built, I had bad
  112.        feelings about it.  The whole thing felt entirely too much like a
  113.        band-aid.  I finally figured out what was causing the  problem: I
  114.        was installing lexical scanning software without first explaining
  115.        to you what scanning is all about, and what the alternatives are.
  116.        Up  till  now, I have studiously avoided  giving  you  a  lot  of
  117.        theory,  and  certainly  not  alternatives.    I  generally don't
  118.        respond well to the textbooks that give you twenty-five different
  119.        ways  to do something, but no clue as to which way best fits your
  120.        needs.  I've tried to avoid that pitfall by just showing  you ONE
  121.        method, that WORKS.
  122.  
  123.        But  this is an important area.  While  the  lexical  scanner  is
  124.        hardly the most  exciting  part  of  a compiler, it often has the
  125.        most  profound  effect  on  the  general  "look  & feel"  of  the
  126.        language, since after all it's the  part  closest to the user.  I
  127.        have a particular structure in mind for the scanner  to  be  used
  128.        with  KISS.    It fits the look &  feel  that  I  want  for  thatA*A*
  129.                                      - 2 -
  130.  
  131. PA A
  132.  
  133.  
  134.  
  135.  
  136.  
  137.        language.  But it may not work at  all  for  the  language YOU'RE
  138.        cooking  up,  so  in this one case I feel that it's important for
  139.        you to know your options.
  140.  
  141.        So I'm going to depart, again, from my  usual  format.    In this
  142.        session we'll be getting  much  deeper  than usual into the basic
  143.        theory of languages and  grammars.    I'll  also be talking about
  144.        areas OTHER than compilers in  which  lexical  scanning  plays an
  145.        important role.  Finally, I will show you  some  alternatives for
  146.        the structure of the lexical scanner.  Then, and only  then, will
  147.        we get back to our parser  from  the last installment.  Bear with
  148.        me ... I think you'll find it's worth the wait.    In fact, since
  149.        scanners have many applications  outside  of  compilers,  you may
  150.        well find this to be the most useful session for you.
  151.  
  152.  
  153.        LEXICAL SCANNING
  154.  
  155.        Lexical scanning is the process of scanning the  stream  of input
  156.        characters and separating it  into  strings  called tokens.  Most
  157.        compiler  texts  start  here,  and  devote  several  chapters  to
  158.        discussing various ways to build scanners.  This approach has its
  159.        place, but as you have already  seen,  there  is a lot you can do
  160.        without ever even addressing the issue, and in  fact  the scanner
  161.        we'll  end  up with here won't look  much  like  what  the  texts
  162.        describe.  The reason?    Compiler  theory and, consequently, the
  163.        programs resulting from it, must  deal with the most general kind
  164.        of parsing rules.  We don't.  In the real  world,  it is possible
  165.        to specify the language syntax in such a way that a pretty simple
  166.        scanner will suffice.  And as always, KISS is our motto.
  167.  
  168.        Typically, lexical scanning is  done  in  a  separate part of the
  169.        compiler, so that the parser per  se  sees only a stream of input
  170.        tokens.  Now, theoretically it  is not necessary to separate this
  171.        function from the rest of the parser.  There is  only  one set of
  172.        syntax equations that define the  whole language, so in theory we
  173.        could write the whole parser in one module.
  174.  
  175.        Why  the  separation?      The  answer  has  both  practical  and
  176.        theoretical bases.
  177.  
  178.        In  1956,  Noam  Chomsky  defined  the  "Chomsky   Hierarchy"  of
  179.        grammars.  They are:
  180.  
  181.             o Type 0:  Unrestricted (e.g., English)
  182.  
  183.             o Type 1:  Context-Sensitive
  184.  
  185.             o Type 2:  Context-Free
  186.  
  187.             o Type 3:  Regular
  188.  
  189.        A few features of the typical programming  language (particularly
  190.        the older ones, such as FORTRAN) are Type  1,  but  for  the mostA*A*
  191.                                      - 3 -
  192.  
  193. PA A
  194.  
  195.  
  196.  
  197.  
  198.  
  199.        part  all  modern  languages can be described using only the last
  200.        two types, and those are all we'll be dealing with here.
  201.  
  202.        The  neat  part about these two types  is  that  there  are  very
  203.        specific ways to parse them.  It has been shown that  any regular
  204.        grammar can be parsed using a particular form of abstract machine
  205.        called the state machine (finite  automaton).    We  have already
  206.        implemented state machines in some of our recognizers.
  207.  
  208.        Similarly, Type 2 (context-free) grammars  can  always  be parsed
  209.        using  a  push-down  automaton (a state machine  augmented  by  a
  210.        stack).  We have  also  implemented  these  machines.  Instead of
  211.        implementing  a literal stack, we have  relied  on  the  built-in
  212.        stack associated with recursive coding to do the job, and that in
  213.        fact is the preferred approach for top-down parsing.
  214.  
  215.        Now, it happens that in  real, practical grammars, the parts that
  216.        qualify as  regular expressions tend to be the lower-level parts,
  217.        such as the definition of an identifier:
  218.  
  219.             <ident> ::= <letter> [ <letter> | <digit> ]*
  220.  
  221.        Since it takes a different kind of abstract machine to  parse the
  222.        two  types  of  grammars, it makes sense to separate these lower-
  223.        level functions into  a  separate  module,  the  lexical scanner,
  224.        which is built around the idea of a state machine. The idea is to
  225.        use the simplest parsing technique needed for the job.
  226.  
  227.        There is another, more practical  reason  for  separating scanner
  228.        from  parser.   We like to think of the input source  file  as  a
  229.        stream  of characters, which we process  right  to  left  without
  230.        backtracking.  In practice that  isn't  possible.    Almost every
  231.        language has certain keywords such as  IF,  WHILE, and END.  As I
  232.        mentioned  earlier,    we  can't  really  know  whether  a  given
  233.        character string is a keyword, until we've reached the end of it,
  234.        as defined by a space or other delimiter.  So  in  that sense, we
  235.        MUST  save  the  string long enough to find out whether we have a
  236.        keyword or not.  That's a limited form of backtracking.
  237.  
  238.        So the structure of a conventional compiler involves splitting up
  239.        the functions of  the  lower-level and higher-level parsing.  The
  240.        lexical  scanner  deals  with  things  at  the  character  level,
  241.        collecting characters into strings, etc., and passing  them along
  242.        to the parser proper as indivisible tokens.  It's also considered
  243.        normal to let the scanner have the job of identifying keywords.
  244.  
  245.  
  246.        STATE MACHINES AND ALTERNATIVES
  247.  
  248.        I  mentioned  that  the regular expressions can be parsed using a
  249.        state machine.   In  most  compiler  texts,  and  indeed  in most
  250.        compilers as well, you will find this taken literally.   There is
  251.        typically  a  real  implementation  of  the  state  machine, with
  252.        integers used to define the current state, and a table of actionsA*A*
  253.                                      - 4 -
  254.  
  255. PA A
  256.  
  257.  
  258.  
  259.  
  260.  
  261.        to  take   for  each  combination  of  current  state  and  input
  262.        character.  If you  write  a compiler front end using the popular
  263.        Unix tools LEX and YACC, that's  what  you'll get.  The output of
  264.        LEX is a state machine implemented in C, plus a table  of actions
  265.        corresponding to the input grammar given to LEX.  The YACC output
  266.        is  similar  ...  a canned table-driven parser,  plus  the  table
  267.        corresponding to the language syntax.
  268.  
  269.        That  is  not  the  only  choice,  though.     In   our  previous
  270.        installments, you have seen over and over that it is  possible to
  271.        implement  parsers  without  dealing  specifically  with  tables,
  272.        stacks, or state variables.    In fact, in Installment V I warned
  273.        you that if you  find  yourself needing these things you might be
  274.        doing something wrong, and not taking advantage of  the  power of
  275.        Pascal.  There are basically two ways to define a state machine's
  276.        state: explicitly, with  a  state number or code, and implicitly,
  277.        simply by virtue of the fact that I'm at a  certain  place in the
  278.        code  (if  it's  Tuesday,  this  must be Belgium).  We've  relied
  279.        heavily on the implicit approaches  before,  and  I  think you'll
  280.        find that they work well here, too.
  281.  
  282.        In practice, it may not even be necessary to HAVE  a well-defined
  283.        lexical scanner.  This isn't our first experience at dealing with
  284.        multi-character tokens.   In  Installment  III,  we  extended our
  285.        parser to provide  for  them,  and  we didn't even NEED a lexical
  286.        scanner.    That  was  because  in that narrow context, we  could
  287.        always tell, just  by  looking at the single lookahead character,
  288.        whether  we  were  dealing  with  a  number,  a variable,  or  an
  289.        operator.  In effect, we  built  a  distributed  lexical scanner,
  290.        using procedures GetName and GetNum.
  291.  
  292.        With keywords present,  we  can't know anymore what we're dealing
  293.        with, until the entire token is  read.    This leads us to a more
  294.        localized  scanner; although,  as you will see,  the  idea  of  a
  295.        distributed scanner still has its merits.
  296.  
  297.  
  298.        SOME EXPERIMENTS IN SCANNING
  299.  
  300.        Before  getting  back  to our compiler,  it  will  be  useful  to
  301.        experiment a bit with the general concepts.
  302.  
  303.        Let's  begin with the two definitions most  often  seen  in  real
  304.        programming languages:
  305.  
  306.             <ident> ::= <letter> [ <letter> | <digit> ]*
  307.             <number ::= [<digit>]+
  308.  
  309.        (Remember, the '*' indicates zero or more occurences of the terms
  310.        in brackets, and the '+', one or more.)
  311.  
  312.        We  have already dealt with similar  items  in  Installment  III.
  313.        Let's begin (as usual) with a bare cradle.  Not  surprisingly, we
  314.        are going to need a new recognizer:A*A*
  315.                                      - 5 -
  316.  
  317. PA A
  318.  
  319.  
  320.  
  321.  
  322.  
  323.        {--------------------------------------------------------------}
  324.        { Recognize an Alphanumeric Character }
  325.  
  326.        function IsAlNum(c: char): boolean;
  327.        begin
  328.           IsAlNum := IsAlpha(c) or IsDigit(c);
  329.        end;
  330.        {--------------------------------------------------------------}
  331.  
  332.  
  333.        Using this let's write the following two routines, which are very
  334.        similar to those we've used before:
  335.  
  336.  
  337.        {--------------------------------------------------------------}
  338.        { Get an Identifier }
  339.  
  340.        function GetName: string;
  341.        var x: string[8];
  342.        begin
  343.           x := '';
  344.           if not IsAlpha(Look) then Expected('Name');
  345.           while IsAlNum(Look) do begin
  346.             x := x + UpCase(Look);
  347.             GetChar;
  348.           end;
  349.           GetName := x;
  350.        end;
  351.  
  352.  
  353.        {--------------------------------------------------------------}
  354.        { Get a Number }
  355.  
  356.        function GetNum: string;
  357.        var x: string[16];
  358.        begin
  359.           x := '';
  360.           if not IsDigit(Look) then Expected('Integer');
  361.           while IsDigit(Look) do begin
  362.             x := x + Look;
  363.             GetChar;
  364.           end;
  365.           GetNum := x;
  366.        end;
  367.        {--------------------------------------------------------------}
  368.  
  369.  
  370.        (Notice  that this version of GetNum returns  a  string,  not  an
  371.        integer as before.)
  372.  
  373.        You  can  easily  verify that these routines work by calling them
  374.        from the main program, as inABAB
  375.                                      - 6 -A*A*
  376.  
  377. PA A
  378.  
  379.  
  380.  
  381.  
  382.  
  383.             WriteLn(GetName);
  384.  
  385.        This  program  will  print any legal name typed in (maximum eight
  386.        characters, since that's what we told GetName).   It  will reject
  387.        anything else.
  388.  
  389.        Test the other routine similarly.
  390.  
  391.  
  392.        WHITE SPACE
  393.  
  394.        We  also  have  dealt with embedded white space before, using the
  395.        two  routines  IsWhite  and  SkipWhite.    Make  sure that  these
  396.        routines are in your  current  version of the cradle, and add the
  397.        the line
  398.  
  399.             SkipWhite;
  400.  
  401.        at the end of both GetName and GetNum.
  402.  
  403.        Now, let's define the new procedure:
  404.  
  405.  
  406.        {--------------------------------------------------------------}
  407.        { Lexical Scanner }
  408.  
  409.        Function Scan: string;
  410.        begin
  411.           if IsAlpha(Look) then
  412.              Scan := GetName
  413.           else if IsDigit(Look) then
  414.              Scan := GetNum
  415.           else begin
  416.              Scan := Look;
  417.              GetChar;
  418.           end;
  419.           SkipWhite;
  420.        end;
  421.        {--------------------------------------------------------------}
  422.  
  423.  
  424.        We can call this from the new main program:A>A>
  425.  
  426.  
  427.                                      - 7 -A*A*
  428.  
  429. PA A
  430.  
  431.  
  432.  
  433.  
  434.  
  435.        {--------------------------------------------------------------}
  436.        { Main Program }
  437.  
  438.  
  439.        begin
  440.           Init;
  441.           repeat
  442.              Token := Scan;
  443.              writeln(Token);
  444.           until Token = CR;
  445.        end.
  446.        {--------------------------------------------------------------}
  447.  
  448.  
  449.        (You will have to add the declaration of the string Token  at the
  450.        beginning of the program.  Make it any convenient length,  say 16
  451.        characters.)
  452.  
  453.        Now,  run the program.  Note how the  input  string  is,  indeed,
  454.        separated into distinct tokens.
  455.  
  456.  
  457.        STATE MACHINES
  458.  
  459.        For  the  record,  a  parse  routine  like  GetName  does  indeed
  460.        implement a state machine.  The state is implicit in  the current
  461.        position in the code.  A very useful trick for visualizing what's
  462.        going on is  the  syntax  diagram,  or  "railroad-track" diagram.
  463.        It's a little difficult to draw  one  in this medium, so I'll use
  464.        them very sparingly, but  the  figure  below  should give you the
  465.        idea:
  466.  
  467.  
  468.                   |-----> Other---------------------------> Error
  469.                   |
  470.           Start -------> Letter ---------------> Other -----> Finish
  471.                   ^                        V
  472.                   |                        |
  473.                   |<----- Letter <---------|
  474.                   |                        |
  475.                   |<----- Digit  <----------
  476.  
  477.  
  478.        As  you  can  see,  this  diagram  shows  how  the logic flows as
  479.        characters  are  read.    Things  begin, of course, in the  start
  480.        state, and end when  a  character  other  than an alphanumeric is
  481.        found.  If  the  first  character  is not alpha, an error occurs.
  482.        Otherwise the machine will continue looping until the terminating
  483.        delimiter is found.
  484.  
  485.        Note  that at any point in the flow,  our  position  is  entirely
  486.        dependent on the past  history  of the input characters.  At that
  487.        point, the action to be taken depends only on the  current state,A6A6
  488.                                      - 8 -A*A*
  489.  
  490. PA A
  491.  
  492.  
  493.  
  494.  
  495.  
  496.        plus the current input character.  That's what make this  a state
  497.        machine.
  498.  
  499.        Because of the difficulty of drawing  railroad-track  diagrams in
  500.        this medium, I'll continue to  stick to syntax equations from now
  501.        on.  But I highly recommend the diagrams to you for  anything you
  502.        do that involves parsing.  After a little practice you  can begin
  503.        to  see  how  to  write  a  parser  directly from  the  diagrams.
  504.        Parallel paths get coded into guarded actions (guarded by IF's or
  505.        CASE statements),  serial  paths  into  sequential  calls.   It's
  506.        almost like working from a schematic.
  507.  
  508.        We didn't even discuss SkipWhite, which  was  introduced earlier,
  509.        but it also is a simple state machine, as is GetNum.  So is their
  510.        parent procedure, Scan.  Little machines make big machines.
  511.  
  512.        The neat thing that I'd like  you  to note is how painlessly this
  513.        implicit approach creates these  state  machines.    I personally
  514.        prefer it a lot over the table-driven approach.  It  also results
  515.        is a small, tight, and fast scanner.
  516.  
  517.  
  518.        NEWLINES
  519.  
  520.        Moving right along, let's modify  our scanner to handle more than
  521.        one line.  As I mentioned last time, the most straightforward way
  522.        to  do  this  is to simply treat the newline characters, carriage
  523.        return  and line feed, as white space.  This is, in fact, the way
  524.        the  C  standard  library  routine,  iswhite, works.   We  didn't
  525.        actually try this  before.  I'd like to do it now, so you can get
  526.        a feel for the results.
  527.  
  528.        To do this, simply modify the single executable  line  of IsWhite
  529.        to read:
  530.  
  531.  
  532.           IsWhite := c in [' ', TAB, CR, LF];
  533.  
  534.  
  535.        We need to give the main  program  a new stop condition, since it
  536.        will never see a CR.  Let's just use:
  537.  
  538.  
  539.           until Token = '.';
  540.  
  541.  
  542.        OK, compile this  program  and  run  it.   Try a couple of lines,
  543.        terminated by the period.  I used:
  544.  
  545.  
  546.             now is the time
  547.             for all good men.ABAB
  548.                                      - 9 -A*A*
  549.  
  550. PA A
  551.  
  552.  
  553.  
  554.  
  555.  
  556.        Hey,  what  happened?   When I tried it, I didn't  get  the  last
  557.        token, the period.  The program didn't halt.  What's more, when I
  558.        pressed the  'enter'  key  a  few  times,  I still didn't get the
  559.        period.
  560.  
  561.        If you're still stuck in your program, you'll find that  typing a
  562.        period on a new line will terminate it.
  563.  
  564.        What's going on here?  The answer is  that  we're  hanging  up in
  565.        SkipWhite.  A quick look at  that  routine will show that as long
  566.        as we're typing null lines, we're going to just continue to loop.
  567.        After SkipWhite encounters an LF,  it tries to execute a GetChar.
  568.        But since the input buffer is now empty, GetChar's read statement
  569.        insists  on  having  another  line.    Procedure  Scan  gets  the
  570.        terminating period, all right,  but  it  calls SkipWhite to clean
  571.        up, and SkipWhite won't return until it gets a non-null line.
  572.  
  573.        This kind of behavior is not quite as bad as it seems.  In a real
  574.        compiler,  we'd  be  reading  from  an input file instead of  the
  575.        console, and as long  as  we have some procedure for dealing with
  576.        end-of-files, everything will come out  OK.  But for reading data
  577.        from the console, the behavior is just too bizarre.  The  fact of
  578.        the matter is that the C/Unix convention is  just  not compatible
  579.        with the structure of  our  parser,  which  calls for a lookahead
  580.        character.    The  code that the Bell  wizards  have  implemented
  581.        doesn't use that convention, which is why they need 'ungetc'.
  582.  
  583.        OK, let's fix the problem.  To do that, we need to go back to the
  584.        old definition of IsWhite (delete the CR and  LF  characters) and
  585.        make  use  of  the procedure Fin that I introduced last time.  If
  586.        it's not in your current version of the cradle, put it there now.
  587.  
  588.        Also, modify the main program to read:
  589.  
  590.  
  591.        {--------------------------------------------------------------}
  592.        { Main Program }
  593.  
  594.  
  595.        begin
  596.           Init;
  597.           repeat
  598.              Token := Scan;
  599.              writeln(Token);
  600.              if Token = CR then Fin;
  601.           until Token = '.';
  602.        end.
  603.        {--------------------------------------------------------------}
  604.  
  605.  
  606.        Note the "guard"  test  preceding  the  call to Fin.  That's what
  607.        makes the whole thing work, and ensures that we don't try to read
  608.        a line ahead.A6A6
  609.                                     - 10 -A*A*
  610.  
  611. PA A
  612.  
  613.  
  614.  
  615.  
  616.  
  617.        Try the code now. I think you'll like it better.
  618.  
  619.        If you refer to the code  we  did in the last installment, you'll
  620.        find that I quietly sprinkled calls to Fin  throughout  the code,
  621.        wherever  a line break was appropriate.  This  is  one  of  those
  622.        areas that really affects the look  &  feel that I mentioned.  At
  623.        this  point  I  would  urge  you  to  experiment  with  different
  624.        arrangements  and  see  how  you  like  them.    If you want your
  625.        language  to  be  truly  free-field,  then  newlines   should  be
  626.        transparent.   In  this  case,  the  best  approach is to put the
  627.        following lines at the BEGINNING of Scan:
  628.  
  629.  
  630.                  while Look = CR do
  631.                     Fin;
  632.  
  633.  
  634.        If, on the other  hand,  you  want  a line-oriented language like
  635.        Assembler, BASIC, or FORTRAN  (or  even  Ada...  note that it has
  636.        comments terminated by newlines),  then  you'll  need for Scan to
  637.        return CR's as tokens.  It  must  also  eat the trailing LF.  The
  638.        best way to do that is to use this line,  again  at the beginning
  639.        of Scan:
  640.  
  641.                  if Look = LF then Fin;
  642.  
  643.  
  644.        For other conventions, you'll  have  to  use  other arrangements.
  645.        In my example  of  the  last  session, I allowed newlines only at
  646.        specific places, so I was somewhere in the middle ground.  In the
  647.        rest of these sessions, I'll be picking ways  to  handle newlines
  648.        that I happen to like, but I want you to know how to choose other
  649.        ways for yourselves.
  650.  
  651.  
  652.        OPERATORS
  653.  
  654.        We  could  stop now and have a  pretty  useful  scanner  for  our
  655.        purposes.  In the fragments of KISS that we've built so  far, the
  656.        only tokens that have multiple characters are the identifiers and
  657.        numbers.    All  operators  were  single  characters.   The  only
  658.        exception I can think of is the relops <=, >=,  and  <>, but they
  659.        could be dealt with as special cases.
  660.  
  661.        Still, other languages have  multi-character  operators,  such as
  662.        the ':=' of  Pascal or the '++' and '>>' of C.  So  while  we may
  663.        not need multi-character operators, it's  nice to know how to get
  664.        them if necessary.
  665.  
  666.        Needless to say, we  can  handle operators very much the same way
  667.        as the other tokens.  Let's start with a recognizer:ANAN
  668.                                     - 11 -A*A*
  669.  
  670. PA A
  671.  
  672.  
  673.  
  674.  
  675.  
  676.        {--------------------------------------------------------------}
  677.        { Recognize Any Operator }
  678.  
  679.        function IsOp(c: char): boolean;
  680.        begin
  681.           IsOp := c in ['+', '-', '*', '/', '<', '>', ':', '='];
  682.        end;
  683.        {--------------------------------------------------------------}
  684.  
  685.  
  686.        It's important to  note  that  we  DON'T  have  to  include every
  687.        possible  operator in this list.   For  example,  the  paretheses
  688.        aren't  included, nor is the terminating  period.    The  current
  689.        version of Scan handles single-character operators  just  fine as
  690.        it is.  The list above includes only those  characters  that  can
  691.        appear in multi-character operators.  (For specific languages, of
  692.        course, the list can always be edited.)
  693.  
  694.        Now, let's modify Scan to read:
  695.  
  696.  
  697.        {--------------------------------------------------------------}
  698.        { Lexical Scanner }
  699.  
  700.        Function Scan: string;
  701.        begin
  702.           while Look = CR do
  703.              Fin;
  704.           if IsAlpha(Look) then
  705.              Scan := GetName
  706.           else if IsDigit(Look) then
  707.              Scan := GetNum
  708.           else if IsOp(Look) then
  709.              Scan := GetOp
  710.           else begin
  711.              Scan := Look;
  712.              GetChar;
  713.           end;
  714.           SkipWhite;
  715.        end;
  716.        {--------------------------------------------------------------}
  717.  
  718.  
  719.        Try the program now.  You  will  find that any code fragments you
  720.        care  to throw at it will be neatly  broken  up  into  individual
  721.        tokens.
  722.  
  723.  
  724.        LISTS, COMMAS AND COMMAND LINES
  725.  
  726.        Before getting back to the main thrust of our study, I'd  like to
  727.        get on my soapbox for a moment.ABAB
  728.                                     - 12 -A*A*
  729.  
  730. PA A
  731.  
  732.  
  733.  
  734.  
  735.  
  736.        How many times have you worked with a program or operating system
  737.        that had rigid rules about how you must separate items in a list?
  738.        (Try,  the  last  time  you  used MSDOS!)  Some programs  require
  739.        spaces as delimiters, and  some  require  commas.   Worst of all,
  740.        some  require  both,  in  different  places.    Most  are  pretty
  741.        unforgiving about violations of their rules.
  742.  
  743.        I think this is inexcusable.  It's too  easy  to  write  a parser
  744.        that will handle  both  spaces  and  commas  in  a  flexible way.
  745.        Consider the following procedure:
  746.  
  747.  
  748.        {--------------------------------------------------------------}
  749.        { Skip Over a Comma }
  750.  
  751.        procedure SkipComma;
  752.        begin
  753.           SkipWhite;
  754.           if Look = ',' then begin
  755.              GetChar;
  756.              SkipWhite;
  757.           end;
  758.        end;
  759.        {--------------------------------------------------------------}
  760.  
  761.  
  762.        This eight-line procedure will skip over  a  delimiter consisting
  763.        of any number (including zero)  of spaces, with zero or one comma
  764.        embedded in the string.
  765.  
  766.        TEMPORARILY, change the call to SkipWhite in Scan to  a  call  to
  767.        SkipComma,  and  try  inputting some lists.   Works  nicely,  eh?
  768.        Don't you wish more software authors knew about SkipComma?
  769.  
  770.        For the record, I found that adding the  equivalent  of SkipComma
  771.        to my Z80 assembler-language programs took all of  6  (six) extra
  772.        bytes of  code.    Even  in a 64K machine, that's not a very high
  773.        price to pay for user-friendliness!
  774.  
  775.        I  think  you can see where I'm going here.  Even  if  you  never
  776.        write a line of a compiler code in your life, there are places in
  777.        every program where  you  can  use  the concepts of parsing.  Any
  778.        program that processes a command line needs them.   In  fact,  if
  779.        you  think  about  it for a bit, you'll have to conclude that any
  780.        time  you  write  a program that processes  user  inputs,  you're
  781.        defining a  language.  People communicate with languages, and the
  782.        syntax implicit in your program  defines that language.  The real
  783.        question  is:  are  you  going  to  define  it  deliberately  and
  784.        explicitly, or just let it turn out to be  whatever  the  program
  785.        ends up parsing?
  786.  
  787.        I claim that you'll have  a better, more user-friendly program if
  788.        you'll take the time to define the syntax explicitly.  Write down
  789.        the syntax equations or  draw  the  railroad-track  diagrams, andA*A*
  790.                                     - 13 -
  791.  
  792. PA A
  793.  
  794.  
  795.  
  796.  
  797.  
  798.        code the parser using the techniques I've shown you here.  You'll
  799.        end  up with a better program, and it will be easier to write, to
  800.        boot.
  801.  
  802.  
  803.        GETTING FANCY
  804.  
  805.        OK, at this point we have a pretty nice lexical scanner that will
  806.        break  an  input stream up into tokens.  We could use  it  as  it
  807.        stands and have a servicable compiler.  But there are  some other
  808.        aspects of lexical scanning that we need to cover.
  809.  
  810.        The main consideration is <shudder> efficiency.  Remember when we
  811.        were dealing  with  single-character  tokens,  every  test  was a
  812.        comparison of a single character, Look, with a byte constant.  We
  813.        also used the Case statement heavily.
  814.  
  815.        With the multi-character tokens being returned by Scan, all those
  816.        tests now become string comparisons.  Much slower.  And  not only
  817.        slower, but more awkward, since  there is no string equivalent of
  818.        the  Case  statement  in Pascal.  It seems especially wasteful to
  819.        test for what used to be single characters ... the '=',  '+', and
  820.        other operators ... using string comparisons.
  821.  
  822.        Using string comparison is not  impossible ... Ron Cain used just
  823.        that approach in writing Small C.  Since we're  sticking  to  the
  824.        KISS principle here, we would  be truly justified in settling for
  825.        this  approach.    But then I would have failed to tell you about
  826.        one of the key approaches used in "real" compilers.
  827.  
  828.        You have to remember: the lexical scanner is going to be called a
  829.        _LOT_!   Once for every token in the  whole  source  program,  in
  830.        fact.   Experiments  have  indicated  that  the  average compiler
  831.        spends  anywhere  from 20% to 40% of  its  time  in  the  scanner
  832.        routines.  If there were ever a place  where  efficiency deserves
  833.        real consideration, this is it.
  834.  
  835.        For this reason, most compiler writers ask the lexical scanner to
  836.        do  a  little  more work, by "tokenizing"  the input stream.  The
  837.        idea  is  to  match every token  against  a  list  of  acceptable
  838.        keywords  and operators, and return unique  codes  for  each  one
  839.        recognized.  In the case of ordinary variable  names  or numbers,
  840.        we  just return a code that says what kind of token they are, and
  841.        save the actual string somewhere else.
  842.  
  843.        One  of the first things we're going to need is a way to identify
  844.        keywords.  We can always do  it  with successive IF tests, but it
  845.        surely would be nice  if  we  had  a general-purpose routine that
  846.        could compare a given string with  a  table of keywords.  (By the
  847.        way, we're also going  to  need such a routine later, for dealing
  848.        with symbol tables.)  This  usually presents a problem in Pascal,
  849.        because standard Pascal  doesn't  allow  for  arrays  of variable
  850.        lengths.   It's  a  real  bother  to  have to declare a differentA6A6
  851.                                     - 14 -A*A*
  852.  
  853. PA A
  854.  
  855.  
  856.  
  857.  
  858.  
  859.        search routine for every table.    Standard  Pascal  also doesn't
  860.        allow for initializing arrays, so you tend to see code like
  861.  
  862.             Table[1] := 'IF';
  863.             Table[2] := 'ELSE';
  864.             .
  865.             .
  866.             Table[n] := 'END';
  867.  
  868.        which can get pretty old if there are many keywords.
  869.  
  870.        Fortunately, Turbo Pascal 4.0 has extensions that  eliminate both
  871.        of  these  problems.   Constant arrays can be declared using TP's
  872.        "typed constant" facility, and  the  variable  dimensions  can be
  873.        handled with its C-like extensions for pointers.
  874.  
  875.        First, modify your declarations like this:
  876.  
  877.  
  878.        {--------------------------------------------------------------}
  879.        { Type Declarations  }
  880.  
  881.        type Symbol = string[8];
  882.  
  883.             SymTab = array[1..1000] of Symbol;
  884.  
  885.             TabPtr = ^SymTab;
  886.        {--------------------------------------------------------------}
  887.  
  888.  
  889.        (The dimension  used  in  SymTab  is  not  real ... no storage is
  890.        allocated by the declaration itself,  and the number need only be
  891.        "big enough.")
  892.  
  893.        Now, just beneath those declarations, add the following:
  894.  
  895.  
  896.        {--------------------------------------------------------------}
  897.        { Definition of Keywords and Token Types }
  898.  
  899.        const KWlist: array [1..4] of Symbol =
  900.                      ('IF', 'ELSE', 'ENDIF', 'END');
  901.  
  902.        {--------------------------------------------------------------}
  903.  
  904.  
  905.        Next, insert the following new function:
  906.  
  907.  
  908.        {--------------------------------------------------------------}
  909.        { Table Lookup }
  910.  
  911.        { If the input string matches a table entry, return the entry
  912.          index.  If not, return a zero.  }A*A*
  913.                                     - 15 -
  914.  
  915. PA A
  916.  
  917.  
  918.  
  919.  
  920.  
  921.        function Lookup(T: TabPtr; s: string; n: integer): integer;
  922.        var i: integer;
  923.            found: boolean;
  924.        begin
  925.           found := false;
  926.           i := n;
  927.           while (i > 0) and not found do
  928.              if s = T^[i] then
  929.                 found := true
  930.              else
  931.                 dec(i);
  932.           Lookup := i;
  933.        end;
  934.        {--------------------------------------------------------------}
  935.  
  936.  
  937.        To test it,  you  can  temporarily  change  the  main  program as
  938.        follows:
  939.  
  940.  
  941.        {--------------------------------------------------------------}
  942.        { Main Program }
  943.  
  944.  
  945.        begin
  946.           ReadLn(Token);
  947.           WriteLn(Lookup(Addr(KWList), Token, 4));
  948.        end.
  949.        {--------------------------------------------------------------}
  950.  
  951.  
  952.        Notice how Lookup is called: The Addr function sets up  a pointer
  953.        to KWList, which gets passed to Lookup.
  954.  
  955.        OK, give this  a  try.    Since we're bypassing Scan here, you'll
  956.        have to type the keywords in upper case to get any matches.
  957.  
  958.        Now that we can recognize keywords, the next thing is  to arrange
  959.        to return codes for them.
  960.  
  961.        So what kind of code should we return?  There are really only two
  962.        reasonable choices.  This seems like an ideal application for the
  963.        Pascal enumerated type.   For  example,  you can define something
  964.        like
  965.  
  966.             SymType = (IfSym, ElseSym, EndifSym, EndSym, Ident, Number,
  967.                            Operator);
  968.  
  969.        and arrange to return a variable of this type.   Let's  give it a
  970.        try.  Insert the line above into your type definitions.
  971.  
  972.        Now, add the two variable declarations:ABAB
  973.                                     - 16 -A*A*
  974.  
  975. PA A
  976.  
  977.  
  978.  
  979.  
  980.  
  981.            Token: Symtype;          { Current Token  }
  982.            Value: String[16];       { String Token of Look }
  983.  
  984.  
  985.        Modify the scanner to read:
  986.  
  987.  
  988.        {--------------------------------------------------------------}
  989.        { Lexical Scanner }
  990.  
  991.        procedure Scan;
  992.        var k: integer;
  993.        begin
  994.           while Look = CR do
  995.              Fin;
  996.           if IsAlpha(Look) then begin
  997.              Value := GetName;
  998.              k := Lookup(Addr(KWlist), Value, 4);
  999.              if k = 0 then
  1000.                 Token := Ident
  1001.              else
  1002.                 Token := SymType(k - 1);
  1003.              end
  1004.           else if IsDigit(Look) then begin
  1005.              Value := GetNum;
  1006.              Token := Number;
  1007.              end
  1008.           else if IsOp(Look) then begin
  1009.              Value := GetOp;
  1010.              Token := Operator;
  1011.              end
  1012.           else begin
  1013.              Value := Look;
  1014.              Token := Operator;
  1015.              GetChar;
  1016.           end;
  1017.           SkipWhite;
  1018.        end;
  1019.        {--------------------------------------------------------------}
  1020.  
  1021.  
  1022.        (Notice that Scan is now a procedure, not a function.)
  1023.  
  1024.  
  1025.        Finally, modify the main program to read:AKAK
  1026.  
  1027.                                     - 17 -A*A*
  1028.  
  1029. PA A
  1030.  
  1031.  
  1032.  
  1033.  
  1034.  
  1035.        {--------------------------------------------------------------}
  1036.        { Main Program }
  1037.  
  1038.        begin
  1039.           Init;
  1040.           repeat
  1041.              Scan;
  1042.              case Token of
  1043.                Ident: write('Ident ');
  1044.                Number: Write('Number ');
  1045.                Operator: Write('Operator ');
  1046.                IfSym, ElseSym, EndifSym, EndSym: Write('Keyword ');
  1047.              end;
  1048.              Writeln(Value);
  1049.           until Token = EndSym;
  1050.        end.
  1051.        {--------------------------------------------------------------}
  1052.  
  1053.  
  1054.        What we've done here is to replace the string Token  used earlier
  1055.        with an enumerated type. Scan returns the type in variable Token,
  1056.        and returns the string itself in the new variable Value.
  1057.  
  1058.        OK, compile this and give it a whirl.  If everything  goes right,
  1059.        you should see that we are now recognizing keywords.
  1060.  
  1061.        What  we  have  now is working right, and it was easy to generate
  1062.        from what  we  had  earlier.    However,  it still seems a little
  1063.        "busy" to me.  We can  simplify  things a bit by letting GetName,
  1064.        GetNum, GetOp, and Scan be  procedures  working  with  the global
  1065.        variables Token and Value, thereby eliminating the  local copies.
  1066.        It  also seems a little cleaner to move  the  table  lookup  into
  1067.        GetName.  The new form for the four procedures is, then:
  1068.  
  1069.  
  1070.        {--------------------------------------------------------------}
  1071.        { Get an Identifier }
  1072.  
  1073.        procedure GetName;
  1074.        var k: integer;
  1075.        begin
  1076.           Value := '';
  1077.           if not IsAlpha(Look) then Expected('Name');
  1078.           while IsAlNum(Look) do begin
  1079.             Value := Value + UpCase(Look);
  1080.             GetChar;
  1081.           end;
  1082.           k := Lookup(Addr(KWlist), Value, 4);
  1083.           if k = 0 then
  1084.              Token := Ident
  1085.           else
  1086.              Token := SymType(k-1);
  1087.        end;A6A6
  1088.                                     - 18 -A*A*
  1089.  
  1090. PA A
  1091.  
  1092.  
  1093.  
  1094.  
  1095.  
  1096.        {--------------------------------------------------------------}
  1097.        { Get a Number }
  1098.  
  1099.        procedure GetNum;
  1100.        begin
  1101.           Value := '';
  1102.           if not IsDigit(Look) then Expected('Integer');
  1103.           while IsDigit(Look) do begin
  1104.             Value := Value + Look;
  1105.             GetChar;
  1106.           end;
  1107.           Token := Number;
  1108.        end;
  1109.  
  1110.  
  1111.        {--------------------------------------------------------------}
  1112.        { Get an Operator }
  1113.  
  1114.        procedure GetOp;
  1115.        begin
  1116.           Value := '';
  1117.           if not IsOp(Look) then Expected('Operator');
  1118.           while IsOp(Look) do begin
  1119.             Value := Value + Look;
  1120.             GetChar;
  1121.           end;
  1122.           Token := Operator;
  1123.        end;
  1124.  
  1125.  
  1126.        {--------------------------------------------------------------}
  1127.        { Lexical Scanner }
  1128.  
  1129.        procedure Scan;
  1130.        var k: integer;
  1131.        begin
  1132.           while Look = CR do
  1133.              Fin;
  1134.           if IsAlpha(Look) then
  1135.              GetName
  1136.           else if IsDigit(Look) then
  1137.              GetNum
  1138.           else if IsOp(Look) then
  1139.              GetOp
  1140.           else begin
  1141.              Value := Look;
  1142.              Token := Operator;
  1143.              GetChar;
  1144.           end;
  1145.           SkipWhite;
  1146.        end;
  1147.        {--------------------------------------------------------------}ABAB
  1148.                                     - 19 -A*A*
  1149.  
  1150. PA A
  1151.  
  1152.  
  1153.  
  1154.  
  1155.  
  1156.        RETURNING A CHARACTER
  1157.  
  1158.        Essentially  every scanner I've ever seen  that  was  written  in
  1159.        Pascal  used  the  mechanism of an enumerated type that I've just
  1160.        described.  It is certainly  a workable mechanism, but it doesn't
  1161.        seem the simplest approach to me.
  1162.  
  1163.        For one thing, the  list  of possible symbol types can get pretty
  1164.        long. Here, I've used just one symbol, "Operator,"  to  stand for
  1165.        all of the operators, but I've seen other  designs  that actually
  1166.        return different codes for each one.
  1167.  
  1168.        There is, of course, another simple type that can be  returned as
  1169.        a  code: the character.  Instead  of  returning  the  enumeration
  1170.        value 'Operator' for a '+' sign, what's wrong with just returning
  1171.        the character itself?  A character is just as good a variable for
  1172.        encoding the different  token  types,  it  can  be  used  in case
  1173.        statements  easily, and it's sure a lot easier  to  type.    What
  1174.        could be simpler?
  1175.  
  1176.        Besides, we've already  had  experience with the idea of encoding
  1177.        keywords as single characters.  Our previous programs are already
  1178.        written  that  way,  so  using  this approach will  minimize  the
  1179.        changes to what we've already done.
  1180.  
  1181.        Some of you may feel that this idea of returning  character codes
  1182.        is too mickey-mouse.  I must  admit  it gets a little awkward for
  1183.        multi-character operators like '<='.   If you choose to stay with
  1184.        the  enumerated  type,  fine.  For the rest, I'd like to show you
  1185.        how to change what we've done above to support that approach.
  1186.  
  1187.        First, you can delete the SymType declaration now ... we won't be
  1188.        needing that.  And you can change the type of Token to char.
  1189.  
  1190.        Next, to replace SymType, add the following constant string:
  1191.  
  1192.  
  1193.           const KWcode: string[5] = 'xilee';
  1194.  
  1195.  
  1196.        (I'll be encoding all idents with the single character 'x'.)
  1197.  
  1198.  
  1199.        Lastly, modify Scan and its relatives as follows:AQAQ
  1200.  
  1201.                                     - 20 -A*A*
  1202.  
  1203. PA A
  1204.  
  1205.  
  1206.  
  1207.  
  1208.  
  1209.        {--------------------------------------------------------------}
  1210.        { Get an Identifier }
  1211.  
  1212.        procedure GetName;
  1213.        begin
  1214.           Value := '';
  1215.           if not IsAlpha(Look) then Expected('Name');
  1216.           while IsAlNum(Look) do begin
  1217.             Value := Value + UpCase(Look);
  1218.             GetChar;
  1219.           end;
  1220.           Token := KWcode[Lookup(Addr(KWlist), Value, 4) + 1];
  1221.        end;
  1222.  
  1223.  
  1224.        {--------------------------------------------------------------}
  1225.        { Get a Number }
  1226.  
  1227.        procedure GetNum;
  1228.        begin
  1229.           Value := '';
  1230.           if not IsDigit(Look) then Expected('Integer');
  1231.           while IsDigit(Look) do begin
  1232.             Value := Value + Look;
  1233.             GetChar;
  1234.           end;
  1235.           Token := '#';
  1236.        end;
  1237.  
  1238.  
  1239.        {--------------------------------------------------------------}
  1240.        { Get an Operator }
  1241.  
  1242.        procedure GetOp;
  1243.        begin
  1244.           Value := '';
  1245.           if not IsOp(Look) then Expected('Operator');
  1246.           while IsOp(Look) do begin
  1247.             Value := Value + Look;
  1248.             GetChar;
  1249.           end;
  1250.           if Length(Value) = 1 then
  1251.              Token := Value[1]
  1252.           else
  1253.              Token := '?';
  1254.        end;AEAE
  1255.  
  1256.                                     - 21 -A*A*
  1257.  
  1258. PA A
  1259.  
  1260.  
  1261.  
  1262.  
  1263.  
  1264.        {--------------------------------------------------------------}
  1265.        { Lexical Scanner }
  1266.  
  1267.        procedure Scan;
  1268.        var k: integer;
  1269.        begin
  1270.           while Look = CR do
  1271.              Fin;
  1272.           if IsAlpha(Look) then
  1273.              GetName
  1274.           else if IsDigit(Look) then
  1275.              GetNum
  1276.           else if IsOp(Look) then begin
  1277.              GetOp
  1278.           else begin
  1279.              Value := Look;
  1280.              Token := '?';
  1281.              GetChar;
  1282.           end;
  1283.           SkipWhite;
  1284.        end;
  1285.  
  1286.  
  1287.        {--------------------------------------------------------------}
  1288.        { Main Program }
  1289.  
  1290.  
  1291.        begin
  1292.           Init;
  1293.           repeat
  1294.              Scan;
  1295.              case Token of
  1296.                'x': write('Ident ');
  1297.                '#': Write('Number ');
  1298.                'i', 'l', 'e': Write('Keyword ');
  1299.                else Write('Operator ');
  1300.              end;
  1301.              Writeln(Value);
  1302.           until Value = 'END';
  1303.        end.
  1304.        {--------------------------------------------------------------}
  1305.  
  1306.  
  1307.        This program should  work  the  same  as the previous version.  A
  1308.        minor  difference  in  structure,  maybe,  but   it   seems  more
  1309.        straightforward to me.
  1310.  
  1311.  
  1312.        DISTRIBUTED vs CENTRALIZED SCANNERS
  1313.  
  1314.        The structure for the lexical scanner that I've just shown you is
  1315.        very conventional, and  about  99% of all compilers use something
  1316.        very  close  to it.  This is  not,  however,  the  only  possible
  1317.        structure, or even always the best one.A*A*
  1318.                                     - 22 -
  1319.  
  1320. PA A
  1321.  
  1322.  
  1323.  
  1324.  
  1325.  
  1326.        The problem with the  conventional  approach  is that the scanner
  1327.        has no knowledge of context.  For example,  it  can't distinguish
  1328.        between the assignment operator '=' and  the  relational operator
  1329.        '=' (perhaps that's why both C and Pascal  use  different strings
  1330.        for the  two).    All  the scanner can do is to pass the operator
  1331.        along  to  the  parser, which can hopefully tell from the context
  1332.        which operator is meant.    Similarly, a keyword like 'IF' has no
  1333.        place in the middle of a  math  expression, but if one happens to
  1334.        appear there, the scanner  will  see no problem with it, and will
  1335.        return it to the parser, properly encoded as an 'IF'.
  1336.  
  1337.        With this  kind  of  approach,  we  are  not really using all the
  1338.        information at our disposal.  In the middle of an expression, for
  1339.        example, the parser  "knows"  that  there  is no need to look for
  1340.        keywords,  but it has no way of telling the scanner that.  So the
  1341.        scanner  continues to do so.  This, of  course,  slows  down  the
  1342.        compilation.
  1343.  
  1344.        In real-world compilers, the  designers  often  arrange  for more
  1345.        information  to be passed between parser  and  scanner,  just  to
  1346.        avoid  this  kind of problem.  But  that  can  get  awkward,  and
  1347.        certainly destroys a lot of the modularity of the structure.
  1348.  
  1349.        The  alternative  is  to seek some  way  to  use  the  contextual
  1350.        information that comes from knowing where we are  in  the parser.
  1351.        This leads us  back  to  the  notion of a distributed scanner, in
  1352.        which various portions  of  the scanner are called depending upon
  1353.        the context.
  1354.  
  1355.        In KISS, as  in  most  languages,  keywords  ONLY  appear  at the
  1356.        beginning of a statement.  In places like  expressions,  they are
  1357.        not allowed.  Also, with one minor exception (the multi-character
  1358.        relops)  that  is  easily  handled,  all  operators   are  single
  1359.        characters, which means that we don't need GetOp at all.
  1360.  
  1361.        So it turns out  that  even  with  multi-character tokens, we can
  1362.        still always tell from the  current  lookahead  character exactly
  1363.        what kind of token is coming,  except  at the very beginning of a
  1364.        statement.
  1365.  
  1366.        Even at that point, the ONLY  kind  of  token we can accept is an
  1367.        identifier.  We need only to determine if that  identifier  is  a
  1368.        keyword or the target of an assignment statement.
  1369.  
  1370.        We end up, then, still needing only GetName and GetNum, which are
  1371.        used very much as we've used them in earlier installments.
  1372.  
  1373.        It may seem  at first to you that this is a step backwards, and a
  1374.        rather  primitive  approach.   In fact, it is an improvement over
  1375.        the classical scanner, since we're  using  the  scanning routines
  1376.        only where they're really needed.  In places  where  keywords are
  1377.        not allowed, we don't slow things down by looking for them.ABAB
  1378.                                     - 23 -A*A*
  1379.  
  1380. PA A
  1381.  
  1382.  
  1383.  
  1384.  
  1385.  
  1386.        MERGING SCANNER AND PARSER
  1387.  
  1388.        Now that we've covered  all  of the theory and general aspects of
  1389.        lexical scanning that we'll be needing, I'm FINALLY ready to back
  1390.        up my claim that  we  can  accomodate multi-character tokens with
  1391.        minimal change to our previous work.  To keep  things  short  and
  1392.        simple I will restrict myself here to a subset of what we've done
  1393.        before; I'm allowing only one control construct (the  IF)  and no
  1394.        Boolean expressions.  That's enough to demonstrate the parsing of
  1395.        both keywords and expressions.  The extension to the full  set of
  1396.        constructs should be  pretty  apparent  from  what  we've already
  1397.        done.
  1398.  
  1399.        All  the  elements  of  the  program to parse this subset,  using
  1400.        single-character tokens, exist  already in our previous programs.
  1401.        I built it by judicious copying of these files,  but  I  wouldn't
  1402.        dare try to lead you through that process.  Instead, to avoid any
  1403.        confusion, the whole program is shown below:
  1404.  
  1405.  
  1406.        {--------------------------------------------------------------}
  1407.        program KISS;
  1408.  
  1409.        {--------------------------------------------------------------}
  1410.        { Constant Declarations }
  1411.  
  1412.        const TAB = ^I;
  1413.              CR  = ^M;
  1414.              LF  = ^J;
  1415.  
  1416.        {--------------------------------------------------------------}
  1417.        { Type Declarations  }
  1418.  
  1419.        type Symbol = string[8];
  1420.  
  1421.             SymTab = array[1..1000] of Symbol;
  1422.  
  1423.             TabPtr = ^SymTab;
  1424.  
  1425.  
  1426.        {--------------------------------------------------------------}
  1427.        { Variable Declarations }
  1428.  
  1429.        var Look  : char;              { Lookahead Character }
  1430.            Lcount: integer;           { Label Counter       }
  1431.  
  1432.  
  1433.        {--------------------------------------------------------------}
  1434.        { Read New Character From Input Stream }
  1435.  
  1436.        procedure GetChar;
  1437.        begin
  1438.           Read(Look);
  1439.        end;A*A*
  1440.                                     - 24 -
  1441.  
  1442. PA A
  1443.  
  1444.  
  1445.  
  1446.  
  1447.  
  1448.        {--------------------------------------------------------------}
  1449.        { Report an Error }
  1450.  
  1451.        procedure Error(s: string);
  1452.        begin
  1453.           WriteLn;
  1454.           WriteLn(^G, 'Error: ', s, '.');
  1455.        end;
  1456.  
  1457.  
  1458.        {--------------------------------------------------------------}
  1459.        { Report Error and Halt }
  1460.  
  1461.        procedure Abort(s: string);
  1462.        begin
  1463.           Error(s);
  1464.           Halt;
  1465.        end;
  1466.  
  1467.  
  1468.        {--------------------------------------------------------------}
  1469.        { Report What Was Expected }
  1470.  
  1471.        procedure Expected(s: string);
  1472.        begin
  1473.           Abort(s + ' Expected');
  1474.        end;
  1475.  
  1476.        {--------------------------------------------------------------}
  1477.        { Recognize an Alpha Character }
  1478.  
  1479.        function IsAlpha(c: char): boolean;
  1480.        begin
  1481.           IsAlpha := UpCase(c) in ['A'..'Z'];
  1482.        end;
  1483.  
  1484.  
  1485.        {--------------------------------------------------------------}
  1486.        { Recognize a Decimal Digit }
  1487.  
  1488.        function IsDigit(c: char): boolean;
  1489.        begin
  1490.           IsDigit := c in ['0'..'9'];
  1491.        end;
  1492.  
  1493.  
  1494.        {--------------------------------------------------------------}
  1495.        { Recognize an AlphaNumeric Character }
  1496.  
  1497.        function IsAlNum(c: char): boolean;
  1498.        begin
  1499.           IsAlNum := IsAlpha(c) or IsDigit(c);
  1500.        end;A6A6
  1501.                                     - 25 -A*A*
  1502.  
  1503. PA A
  1504.  
  1505.  
  1506.  
  1507.  
  1508.  
  1509.        {--------------------------------------------------------------}
  1510.        { Recognize an Addop }
  1511.  
  1512.        function IsAddop(c: char): boolean;
  1513.        begin
  1514.           IsAddop := c in ['+', '-'];
  1515.        end;
  1516.  
  1517.  
  1518.        {--------------------------------------------------------------}
  1519.        { Recognize a Mulop }
  1520.  
  1521.        function IsMulop(c: char): boolean;
  1522.        begin
  1523.           IsMulop := c in ['*', '/'];
  1524.        end;
  1525.  
  1526.  
  1527.        {--------------------------------------------------------------}
  1528.        { Recognize White Space }
  1529.  
  1530.        function IsWhite(c: char): boolean;
  1531.        begin
  1532.           IsWhite := c in [' ', TAB];
  1533.        end;
  1534.  
  1535.  
  1536.        {--------------------------------------------------------------}
  1537.        { Skip Over Leading White Space }
  1538.  
  1539.        procedure SkipWhite;
  1540.        begin
  1541.           while IsWhite(Look) do
  1542.              GetChar;
  1543.        end;
  1544.  
  1545.  
  1546.        {--------------------------------------------------------------}
  1547.        { Match a Specific Input Character }
  1548.  
  1549.        procedure Match(x: char);
  1550.        begin
  1551.           if Look <> x then Expected('''' + x + '''');
  1552.           GetChar;
  1553.           SkipWhite;
  1554.        end;
  1555.  
  1556.  
  1557.        {--------------------------------------------------------------}
  1558.        { Skip a CRLF }
  1559.  
  1560.        procedure Fin;
  1561.        begin
  1562.           if Look = CR then GetChar;A*A*
  1563.                                     - 26 -
  1564.  
  1565. PA A
  1566.  
  1567.  
  1568.  
  1569.  
  1570.  
  1571.           if Look = LF then GetChar;
  1572.           SkipWhite;
  1573.        end;
  1574.  
  1575.  
  1576.        {--------------------------------------------------------------}
  1577.        { Get an Identifier }
  1578.  
  1579.        function GetName: char;
  1580.        begin
  1581.           while Look = CR do
  1582.              Fin;
  1583.           if not IsAlpha(Look) then Expected('Name');
  1584.           Getname := UpCase(Look);
  1585.           GetChar;
  1586.           SkipWhite;
  1587.        end;
  1588.  
  1589.  
  1590.        {--------------------------------------------------------------}
  1591.        { Get a Number }
  1592.  
  1593.        function GetNum: char;
  1594.        begin
  1595.           if not IsDigit(Look) then Expected('Integer');
  1596.           GetNum := Look;
  1597.           GetChar;
  1598.           SkipWhite;
  1599.        end;
  1600.  
  1601.  
  1602.        {--------------------------------------------------------------}
  1603.        { Generate a Unique Label }
  1604.  
  1605.        function NewLabel: string;
  1606.        var S: string;
  1607.        begin
  1608.           Str(LCount, S);
  1609.           NewLabel := 'L' + S;
  1610.           Inc(LCount);
  1611.        end;
  1612.  
  1613.  
  1614.        {--------------------------------------------------------------}
  1615.        { Post a Label To Output }
  1616.  
  1617.        procedure PostLabel(L: string);
  1618.        begin
  1619.           WriteLn(L, ':');
  1620.        end;
  1621.  
  1622.  
  1623.        {--------------------------------------------------------------}
  1624.        { Output a String with Tab }A*A*
  1625.                                     - 27 -
  1626.  
  1627. PA A
  1628.  
  1629.  
  1630.  
  1631.  
  1632.  
  1633.        procedure Emit(s: string);
  1634.        begin
  1635.           Write(TAB, s);
  1636.        end;
  1637.  
  1638.  
  1639.        {--------------------------------------------------------------}
  1640.  
  1641.        { Output a String with Tab and CRLF }
  1642.  
  1643.        procedure EmitLn(s: string);
  1644.        begin
  1645.           Emit(s);
  1646.           WriteLn;
  1647.        end;
  1648.  
  1649.  
  1650.        {---------------------------------------------------------------}
  1651.        { Parse and Translate an Identifier }
  1652.  
  1653.        procedure Ident;
  1654.        var Name: char;
  1655.        begin
  1656.           Name := GetName;
  1657.           if Look = '(' then begin
  1658.              Match('(');
  1659.              Match(')');
  1660.              EmitLn('BSR ' + Name);
  1661.              end
  1662.           else
  1663.              EmitLn('MOVE ' + Name + '(PC),D0');
  1664.        end;
  1665.  
  1666.  
  1667.        {---------------------------------------------------------------}
  1668.        { Parse and Translate a Math Factor }
  1669.  
  1670.        procedure Expression; Forward;
  1671.  
  1672.        procedure Factor;
  1673.        begin
  1674.           if Look = '(' then begin
  1675.              Match('(');
  1676.              Expression;
  1677.              Match(')');
  1678.              end
  1679.           else if IsAlpha(Look) then
  1680.              Ident
  1681.           else
  1682.              EmitLn('MOVE #' + GetNum + ',D0');
  1683.        end;
  1684.  
  1685.  
  1686.        {---------------------------------------------------------------}A*A*
  1687.                                     - 28 -
  1688.  
  1689. PA A
  1690.  
  1691.  
  1692.  
  1693.  
  1694.  
  1695.        { Parse and Translate the First Math Factor }
  1696.  
  1697.  
  1698.        procedure SignedFactor;
  1699.        var s: boolean;
  1700.        begin
  1701.           s := Look = '-';
  1702.           if IsAddop(Look) then begin
  1703.              GetChar;
  1704.              SkipWhite;
  1705.           end;
  1706.           Factor;
  1707.           if s then
  1708.              EmitLn('NEG D0');
  1709.        end;
  1710.  
  1711.  
  1712.        {--------------------------------------------------------------}
  1713.        { Recognize and Translate a Multiply }
  1714.  
  1715.        procedure Multiply;
  1716.        begin
  1717.           Match('*');
  1718.           Factor;
  1719.           EmitLn('MULS (SP)+,D0');
  1720.        end;
  1721.  
  1722.  
  1723.        {-------------------------------------------------------------}
  1724.        { Recognize and Translate a Divide }
  1725.  
  1726.        procedure Divide;
  1727.        begin
  1728.           Match('/');
  1729.           Factor;
  1730.           EmitLn('MOVE (SP)+,D1');
  1731.           EmitLn('EXS.L D0');
  1732.           EmitLn('DIVS D1,D0');
  1733.        end;
  1734.  
  1735.  
  1736.        {---------------------------------------------------------------}
  1737.        { Completion of Term Processing  (called by Term and FirstTerm }
  1738.  
  1739.        procedure Term1;
  1740.        begin
  1741.           while IsMulop(Look) do begin
  1742.              EmitLn('MOVE D0,-(SP)');
  1743.              case Look of
  1744.               '*': Multiply;
  1745.               '/': Divide;
  1746.              end;
  1747.           end;
  1748.        end;A*A*
  1749.                                     - 29 -
  1750.  
  1751. PA A
  1752.  
  1753.  
  1754.  
  1755.  
  1756.  
  1757.        {---------------------------------------------------------------}
  1758.        { Parse and Translate a Math Term }
  1759.  
  1760.        procedure Term;
  1761.        begin
  1762.           Factor;
  1763.           Term1;
  1764.        end;
  1765.  
  1766.  
  1767.        {---------------------------------------------------------------}
  1768.        { Parse and Translate a Math Term with Possible Leading Sign }
  1769.  
  1770.        procedure FirstTerm;
  1771.        begin
  1772.           SignedFactor;
  1773.           Term1;
  1774.        end;
  1775.  
  1776.  
  1777.        {---------------------------------------------------------------}
  1778.        { Recognize and Translate an Add }
  1779.  
  1780.        procedure Add;
  1781.        begin
  1782.           Match('+');
  1783.           Term;
  1784.           EmitLn('ADD (SP)+,D0');
  1785.        end;
  1786.  
  1787.  
  1788.        {---------------------------------------------------------------}
  1789.        { Recognize and Translate a Subtract }
  1790.  
  1791.        procedure Subtract;
  1792.        begin
  1793.           Match('-');
  1794.           Term;
  1795.           EmitLn('SUB (SP)+,D0');
  1796.           EmitLn('NEG D0');
  1797.        end;
  1798.  
  1799.  
  1800.        {---------------------------------------------------------------}
  1801.        { Parse and Translate an Expression }
  1802.  
  1803.        procedure Expression;
  1804.        begin
  1805.           FirstTerm;
  1806.           while IsAddop(Look) do begin
  1807.              EmitLn('MOVE D0,-(SP)');
  1808.              case Look of
  1809.               '+': Add;
  1810.               '-': Subtract;A*A*
  1811.                                     - 30 -
  1812.  
  1813. PA A
  1814.  
  1815.  
  1816.  
  1817.  
  1818.  
  1819.              end;
  1820.           end;
  1821.        end;
  1822.  
  1823.  
  1824.        {---------------------------------------------------------------}
  1825.        { Parse and Translate a Boolean Condition }
  1826.        { This version is a dummy }
  1827.  
  1828.        Procedure Condition;
  1829.        begin
  1830.           EmitLn('Condition');
  1831.        end;
  1832.  
  1833.  
  1834.        {---------------------------------------------------------------}
  1835.        { Recognize and Translate an IF Construct }
  1836.  
  1837.        procedure Block;
  1838.         Forward;
  1839.  
  1840.        procedure DoIf;
  1841.        var L1, L2: string;
  1842.        begin
  1843.           Match('i');
  1844.           Condition;
  1845.           L1 := NewLabel;
  1846.           L2 := L1;
  1847.           EmitLn('BEQ ' + L1);
  1848.           Block;
  1849.           if Look = 'l' then begin
  1850.              Match('l');
  1851.              L2 := NewLabel;
  1852.              EmitLn('BRA ' + L2);
  1853.              PostLabel(L1);
  1854.              Block;
  1855.           end;
  1856.           PostLabel(L2);
  1857.           Match('e');
  1858.        end;
  1859.  
  1860.  
  1861.        {--------------------------------------------------------------}
  1862.        { Parse and Translate an Assignment Statement }
  1863.  
  1864.        procedure Assignment;
  1865.        var Name: char;
  1866.        begin
  1867.           Name := GetName;
  1868.           Match('=');
  1869.           Expression;
  1870.           EmitLn('LEA ' + Name + '(PC),A0');
  1871.           EmitLn('MOVE D0,(A0)');
  1872.        end;A*A*
  1873.                                     - 31 -
  1874.  
  1875. PA A
  1876.  
  1877.  
  1878.  
  1879.  
  1880.  
  1881.        {--------------------------------------------------------------}
  1882.        { Recognize and Translate a Statement Block }
  1883.  
  1884.        procedure Block;
  1885.        begin
  1886.           while not(Look in ['e', 'l']) do begin
  1887.              case Look of
  1888.               'i': DoIf;
  1889.               CR: while Look = CR do
  1890.                      Fin;
  1891.               else Assignment;
  1892.              end;
  1893.           end;
  1894.        end;
  1895.  
  1896.  
  1897.        {--------------------------------------------------------------}
  1898.        { Parse and Translate a Program }
  1899.  
  1900.        procedure DoProgram;
  1901.        begin
  1902.           Block;
  1903.           if Look <> 'e' then Expected('END');
  1904.           EmitLn('END')
  1905.        end;
  1906.  
  1907.  
  1908.        {--------------------------------------------------------------}
  1909.  
  1910.        { Initialize }
  1911.  
  1912.        procedure Init;
  1913.        begin
  1914.           LCount := 0;
  1915.           GetChar;
  1916.        end;
  1917.  
  1918.  
  1919.        {--------------------------------------------------------------}
  1920.        { Main Program }
  1921.  
  1922.        begin
  1923.           Init;
  1924.           DoProgram;
  1925.        end.
  1926.        {--------------------------------------------------------------}
  1927.  
  1928.  
  1929.        A couple of comments:
  1930.  
  1931.         (1) The form for the expression parser,  using  FirstTerm, etc.,
  1932.             is  a  little  different from what you've seen before.  It's
  1933.             yet another variation on the same theme.  Don't let it throw
  1934.             you ... the change is not required for what follows.A*A*
  1935.                                     - 32 -
  1936.  
  1937. PA A
  1938.  
  1939.  
  1940.  
  1941.  
  1942.  
  1943.         (2) Note that, as usual, I had to add calls to Fin  at strategic
  1944.             spots to allow for multiple lines.
  1945.  
  1946.        Before we proceed to adding the scanner, first copy this file and
  1947.        verify that it does indeed  parse things correctly.  Don't forget
  1948.        the "codes": 'i' for IF, 'l' for ELSE, and 'e' for END or ENDIF.
  1949.  
  1950.        If the program works, then let's press on.  In adding the scanner
  1951.        modules to the program, it helps  to  have a systematic plan.  In
  1952.        all  the  parsers  we've  written  to  date,  we've  stuck  to  a
  1953.        convention that the current lookahead character should  always be
  1954.        a non-blank character.  We  preload  the  lookahead  character in
  1955.        Init, and keep the "pump primed"  after  that.  To keep the thing
  1956.        working right at newlines, we had to modify this a bit  and treat
  1957.        the newline as a legal token.
  1958.  
  1959.        In the  multi-character version, the rule is similar: The current
  1960.        lookahead character should always be left at the BEGINNING of the
  1961.        next token, or at a newline.
  1962.  
  1963.        The multi-character version is shown next.  To get it,  I've made
  1964.        the following changes:
  1965.  
  1966.  
  1967.         o Added the variables Token  and Value, and the type definitions
  1968.           needed by Lookup.
  1969.  
  1970.         o Added the definitions of KWList and KWcode.
  1971.  
  1972.         o Added Lookup.
  1973.  
  1974.         o Replaced GetName and GetNum by their multi-character versions.
  1975.           (Note that the call  to  Lookup has been moved out of GetName,
  1976.           so  that  it  will  not   be  executed  for  calls  within  an
  1977.           expression.)
  1978.  
  1979.         o Created a new,  vestigial  Scan that calls GetName, then scans
  1980.           for keywords.
  1981.  
  1982.         o Created  a  new  procedure,  MatchString,  that  looks  for  a
  1983.           specific keyword.  Note that, unlike  Match,  MatchString does
  1984.           NOT read the next keyword.
  1985.  
  1986.         o Modified Block to call Scan.
  1987.  
  1988.         o Changed the calls  to  Fin  a  bit.   Fin is now called within
  1989.           GetName.
  1990.  
  1991.        Here is the program in its entirety:
  1992.  
  1993.  
  1994.        {--------------------------------------------------------------}
  1995.        program KISS;A6A6
  1996.                                     - 33 -A*A*
  1997.  
  1998. PA A
  1999.  
  2000.  
  2001.  
  2002.  
  2003.  
  2004.        {--------------------------------------------------------------}
  2005.        { Constant Declarations }
  2006.  
  2007.        const TAB = ^I;
  2008.              CR  = ^M;
  2009.              LF  = ^J;
  2010.  
  2011.        {--------------------------------------------------------------}
  2012.        { Type Declarations  }
  2013.  
  2014.        type Symbol = string[8];
  2015.  
  2016.             SymTab = array[1..1000] of Symbol;
  2017.  
  2018.             TabPtr = ^SymTab;
  2019.  
  2020.  
  2021.        {--------------------------------------------------------------}
  2022.        { Variable Declarations }
  2023.  
  2024.        var Look  : char;              { Lookahead Character }
  2025.            Token : char;              { Encoded Token       }
  2026.            Value : string[16];        { Unencoded Token     }
  2027.            Lcount: integer;           { Label Counter       }
  2028.  
  2029.  
  2030.        {--------------------------------------------------------------}
  2031.        { Definition of Keywords and Token Types }
  2032.  
  2033.        const KWlist: array [1..4] of Symbol =
  2034.                      ('IF', 'ELSE', 'ENDIF', 'END');
  2035.  
  2036.        const KWcode: string[5] = 'xilee';
  2037.  
  2038.  
  2039.        {--------------------------------------------------------------}
  2040.        { Read New Character From Input Stream }
  2041.  
  2042.        procedure GetChar;
  2043.        begin
  2044.           Read(Look);
  2045.        end;
  2046.  
  2047.        {--------------------------------------------------------------}
  2048.        { Report an Error }
  2049.  
  2050.        procedure Error(s: string);
  2051.        begin
  2052.           WriteLn;
  2053.           WriteLn(^G, 'Error: ', s, '.');
  2054.        end;
  2055.  
  2056.  
  2057.        {--------------------------------------------------------------}A*A*
  2058.                                     - 34 -
  2059.  
  2060. PA A
  2061.  
  2062.  
  2063.  
  2064.  
  2065.  
  2066.        { Report Error and Halt }
  2067.  
  2068.        procedure Abort(s: string);
  2069.        begin
  2070.           Error(s);
  2071.           Halt;
  2072.        end;
  2073.  
  2074.  
  2075.        {--------------------------------------------------------------}
  2076.        { Report What Was Expected }
  2077.  
  2078.        procedure Expected(s: string);
  2079.        begin
  2080.           Abort(s + ' Expected');
  2081.        end;
  2082.  
  2083.        {--------------------------------------------------------------}
  2084.        { Recognize an Alpha Character }
  2085.  
  2086.        function IsAlpha(c: char): boolean;
  2087.        begin
  2088.           IsAlpha := UpCase(c) in ['A'..'Z'];
  2089.        end;
  2090.  
  2091.  
  2092.        {--------------------------------------------------------------}
  2093.        { Recognize a Decimal Digit }
  2094.  
  2095.        function IsDigit(c: char): boolean;
  2096.        begin
  2097.           IsDigit := c in ['0'..'9'];
  2098.        end;
  2099.  
  2100.  
  2101.        {--------------------------------------------------------------}
  2102.        { Recognize an AlphaNumeric Character }
  2103.  
  2104.        function IsAlNum(c: char): boolean;
  2105.        begin
  2106.           IsAlNum := IsAlpha(c) or IsDigit(c);
  2107.        end;
  2108.  
  2109.  
  2110.        {--------------------------------------------------------------}
  2111.        { Recognize an Addop }
  2112.  
  2113.        function IsAddop(c: char): boolean;
  2114.        begin
  2115.           IsAddop := c in ['+', '-'];
  2116.        end;
  2117.  
  2118.  
  2119.        {--------------------------------------------------------------}A*A*
  2120.                                     - 35 -
  2121.  
  2122. PA A
  2123.  
  2124.  
  2125.  
  2126.  
  2127.  
  2128.        { Recognize a Mulop }
  2129.  
  2130.        function IsMulop(c: char): boolean;
  2131.        begin
  2132.           IsMulop := c in ['*', '/'];
  2133.        end;
  2134.  
  2135.  
  2136.        {--------------------------------------------------------------}
  2137.        { Recognize White Space }
  2138.  
  2139.        function IsWhite(c: char): boolean;
  2140.        begin
  2141.           IsWhite := c in [' ', TAB];
  2142.        end;
  2143.  
  2144.  
  2145.        {--------------------------------------------------------------}
  2146.        { Skip Over Leading White Space }
  2147.  
  2148.        procedure SkipWhite;
  2149.        begin
  2150.           while IsWhite(Look) do
  2151.              GetChar;
  2152.        end;
  2153.  
  2154.  
  2155.        {--------------------------------------------------------------}
  2156.        { Match a Specific Input Character }
  2157.  
  2158.        procedure Match(x: char);
  2159.        begin
  2160.           if Look <> x then Expected('''' + x + '''');
  2161.           GetChar;
  2162.           SkipWhite;
  2163.        end;
  2164.  
  2165.  
  2166.        {--------------------------------------------------------------}
  2167.        { Skip a CRLF }
  2168.  
  2169.        procedure Fin;
  2170.        begin
  2171.           if Look = CR then GetChar;
  2172.           if Look = LF then GetChar;
  2173.           SkipWhite;
  2174.        end;
  2175.  
  2176.  
  2177.        {--------------------------------------------------------------}
  2178.        { Table Lookup }
  2179.  
  2180.        function Lookup(T: TabPtr; s: string; n: integer): integer;
  2181.        var i: integer;A*A*
  2182.                                     - 36 -
  2183.  
  2184. PA A
  2185.  
  2186.  
  2187.  
  2188.  
  2189.  
  2190.            found: boolean;
  2191.        begin
  2192.           found := false;
  2193.           i := n;
  2194.           while (i > 0) and not found do
  2195.              if s = T^[i] then
  2196.                 found := true
  2197.              else
  2198.                 dec(i);
  2199.           Lookup := i;
  2200.        end;
  2201.  
  2202.  
  2203.        {--------------------------------------------------------------}
  2204.        { Get an Identifier }
  2205.  
  2206.        procedure GetName;
  2207.        begin
  2208.           while Look = CR do
  2209.              Fin;
  2210.           if not IsAlpha(Look) then Expected('Name');
  2211.           Value := '';
  2212.           while IsAlNum(Look) do begin
  2213.             Value := Value + UpCase(Look);
  2214.             GetChar;
  2215.           end;
  2216.           SkipWhite;
  2217.        end;
  2218.  
  2219.  
  2220.        {--------------------------------------------------------------}
  2221.        { Get a Number }
  2222.  
  2223.        procedure GetNum;
  2224.        begin
  2225.           if not IsDigit(Look) then Expected('Integer');
  2226.           Value := '';
  2227.           while IsDigit(Look) do begin
  2228.             Value := Value + Look;
  2229.             GetChar;
  2230.           end;
  2231.           Token := '#';
  2232.           SkipWhite;
  2233.        end;
  2234.  
  2235.  
  2236.        {--------------------------------------------------------------}
  2237.        { Get an Identifier and Scan it for Keywords }
  2238.  
  2239.        procedure Scan;
  2240.        begin
  2241.           GetName;
  2242.           Token := KWcode[Lookup(Addr(KWlist), Value, 4) + 1];
  2243.        end;A*A*
  2244.                                     - 37 -
  2245.  
  2246. PA A
  2247.  
  2248.  
  2249.  
  2250.  
  2251.  
  2252.        {--------------------------------------------------------------}
  2253.        { Match a Specific Input String }
  2254.  
  2255.        procedure MatchString(x: string);
  2256.        begin
  2257.           if Value <> x then Expected('''' + x + '''');
  2258.        end;
  2259.  
  2260.  
  2261.        {--------------------------------------------------------------}
  2262.        { Generate a Unique Label }
  2263.  
  2264.        function NewLabel: string;
  2265.        var S: string;
  2266.        begin
  2267.           Str(LCount, S);
  2268.           NewLabel := 'L' + S;
  2269.           Inc(LCount);
  2270.        end;
  2271.  
  2272.  
  2273.        {--------------------------------------------------------------}
  2274.        { Post a Label To Output }
  2275.  
  2276.        procedure PostLabel(L: string);
  2277.        begin
  2278.           WriteLn(L, ':');
  2279.        end;
  2280.  
  2281.  
  2282.        {--------------------------------------------------------------}
  2283.        { Output a String with Tab }
  2284.  
  2285.        procedure Emit(s: string);
  2286.        begin
  2287.           Write(TAB, s);
  2288.        end;
  2289.  
  2290.  
  2291.        {--------------------------------------------------------------}
  2292.        { Output a String with Tab and CRLF }
  2293.  
  2294.        procedure EmitLn(s: string);
  2295.        begin
  2296.           Emit(s);
  2297.           WriteLn;
  2298.        end;
  2299.  
  2300.  
  2301.        {---------------------------------------------------------------}
  2302.        { Parse and Translate an Identifier }
  2303.  
  2304.        procedure Ident;
  2305.        beginA*A*
  2306.                                     - 38 -
  2307.  
  2308. PA A
  2309.  
  2310.  
  2311.  
  2312.  
  2313.  
  2314.           GetName;
  2315.           if Look = '(' then begin
  2316.              Match('(');
  2317.              Match(')');
  2318.              EmitLn('BSR ' + Value);
  2319.              end
  2320.           else
  2321.              EmitLn('MOVE ' + Value + '(PC),D0');
  2322.        end;
  2323.  
  2324.  
  2325.        {---------------------------------------------------------------}
  2326.        { Parse and Translate a Math Factor }
  2327.  
  2328.        procedure Expression; Forward;
  2329.  
  2330.        procedure Factor;
  2331.        begin
  2332.           if Look = '(' then begin
  2333.              Match('(');
  2334.              Expression;
  2335.              Match(')');
  2336.              end
  2337.           else if IsAlpha(Look) then
  2338.              Ident
  2339.           else begin
  2340.              GetNum;
  2341.              EmitLn('MOVE #' + Value + ',D0');
  2342.           end;
  2343.        end;
  2344.  
  2345.  
  2346.        {---------------------------------------------------------------}
  2347.        { Parse and Translate the First Math Factor }
  2348.  
  2349.        procedure SignedFactor;
  2350.        var s: boolean;
  2351.        begin
  2352.           s := Look = '-';
  2353.           if IsAddop(Look) then begin
  2354.              GetChar;
  2355.              SkipWhite;
  2356.           end;
  2357.           Factor;
  2358.           if s then
  2359.              EmitLn('NEG D0');
  2360.        end;
  2361.  
  2362.  
  2363.        {--------------------------------------------------------------}
  2364.        { Recognize and Translate a Multiply }
  2365.  
  2366.        procedure Multiply;
  2367.        beginA*A*
  2368.                                     - 39 -
  2369.  
  2370. PA A
  2371.  
  2372.  
  2373.  
  2374.  
  2375.  
  2376.           Match('*');
  2377.           Factor;
  2378.           EmitLn('MULS (SP)+,D0');
  2379.        end;
  2380.  
  2381.  
  2382.        {-------------------------------------------------------------}
  2383.        { Recognize and Translate a Divide }
  2384.  
  2385.        procedure Divide;
  2386.        begin
  2387.           Match('/');
  2388.           Factor;
  2389.           EmitLn('MOVE (SP)+,D1');
  2390.           EmitLn('EXS.L D0');
  2391.           EmitLn('DIVS D1,D0');
  2392.        end;
  2393.  
  2394.  
  2395.        {---------------------------------------------------------------}
  2396.        { Completion of Term Processing  (called by Term and FirstTerm }
  2397.  
  2398.        procedure Term1;
  2399.        begin
  2400.           while IsMulop(Look) do begin
  2401.              EmitLn('MOVE D0,-(SP)');
  2402.              case Look of
  2403.               '*': Multiply;
  2404.               '/': Divide;
  2405.              end;
  2406.           end;
  2407.        end;
  2408.        {---------------------------------------------------------------}
  2409.        { Parse and Translate a Math Term }
  2410.  
  2411.        procedure Term;
  2412.        begin
  2413.           Factor;
  2414.           Term1;
  2415.        end;
  2416.  
  2417.  
  2418.        {---------------------------------------------------------------}
  2419.        { Parse and Translate a Math Term with Possible Leading Sign }
  2420.  
  2421.        procedure FirstTerm;
  2422.        begin
  2423.           SignedFactor;
  2424.           Term1;
  2425.        end;
  2426.  
  2427.  
  2428.        {---------------------------------------------------------------}
  2429.        { Recognize and Translate an Add }A*A*
  2430.                                     - 40 -
  2431.  
  2432. PA A
  2433.  
  2434.  
  2435.  
  2436.  
  2437.  
  2438.        procedure Add;
  2439.        begin
  2440.           Match('+');
  2441.           Term;
  2442.           EmitLn('ADD (SP)+,D0');
  2443.        end;
  2444.  
  2445.  
  2446.        {---------------------------------------------------------------}
  2447.        { Recognize and Translate a Subtract }
  2448.  
  2449.        procedure Subtract;
  2450.        begin
  2451.           Match('-');
  2452.           Term;
  2453.           EmitLn('SUB (SP)+,D0');
  2454.           EmitLn('NEG D0');
  2455.        end;
  2456.  
  2457.  
  2458.        {---------------------------------------------------------------}
  2459.        { Parse and Translate an Expression }
  2460.  
  2461.        procedure Expression;
  2462.        begin
  2463.           FirstTerm;
  2464.           while IsAddop(Look) do begin
  2465.              EmitLn('MOVE D0,-(SP)');
  2466.              case Look of
  2467.               '+': Add;
  2468.               '-': Subtract;
  2469.              end;
  2470.           end;
  2471.        end;
  2472.  
  2473.  
  2474.        {---------------------------------------------------------------}
  2475.        { Parse and Translate a Boolean Condition }
  2476.        { This version is a dummy }
  2477.  
  2478.        Procedure Condition;
  2479.        begin
  2480.           EmitLn('Condition');
  2481.        end;
  2482.  
  2483.  
  2484.        {---------------------------------------------------------------}
  2485.        { Recognize and Translate an IF Construct }
  2486.  
  2487.        procedure Block; Forward;
  2488.  
  2489.  
  2490.        procedure DoIf;
  2491.        var L1, L2: string;A*A*
  2492.                                     - 41 -
  2493.  
  2494. PA A
  2495.  
  2496.  
  2497.  
  2498.  
  2499.  
  2500.        begin
  2501.           Condition;
  2502.           L1 := NewLabel;
  2503.           L2 := L1;
  2504.           EmitLn('BEQ ' + L1);
  2505.           Block;
  2506.           if Token = 'l' then begin
  2507.              L2 := NewLabel;
  2508.              EmitLn('BRA ' + L2);
  2509.              PostLabel(L1);
  2510.              Block;
  2511.           end;
  2512.           PostLabel(L2);
  2513.           MatchString('ENDIF');
  2514.        end;
  2515.  
  2516.  
  2517.        {--------------------------------------------------------------}
  2518.        { Parse and Translate an Assignment Statement }
  2519.  
  2520.        procedure Assignment;
  2521.        var Name: string;
  2522.        begin
  2523.           Name := Value;
  2524.           Match('=');
  2525.           Expression;
  2526.           EmitLn('LEA ' + Name + '(PC),A0');
  2527.           EmitLn('MOVE D0,(A0)');
  2528.        end;
  2529.  
  2530.  
  2531.        {--------------------------------------------------------------}
  2532.        { Recognize and Translate a Statement Block }
  2533.  
  2534.        procedure Block;
  2535.        begin
  2536.           Scan;
  2537.           while not (Token in ['e', 'l']) do begin
  2538.              case Token of
  2539.               'i': DoIf;
  2540.               else Assignment;
  2541.              end;
  2542.              Scan;
  2543.           end;
  2544.        end;
  2545.  
  2546.  
  2547.        {--------------------------------------------------------------}
  2548.  
  2549.        { Parse and Translate a Program }
  2550.  
  2551.        procedure DoProgram;
  2552.        begin
  2553.           Block;A*A*
  2554.                                     - 42 -
  2555.  
  2556. PA A
  2557.  
  2558.  
  2559.  
  2560.  
  2561.  
  2562.           MatchString('END');
  2563.           EmitLn('END')
  2564.        end;
  2565.  
  2566.  
  2567.        {--------------------------------------------------------------}
  2568.  
  2569.        { Initialize }
  2570.  
  2571.        procedure Init;
  2572.        begin
  2573.           LCount := 0;
  2574.           GetChar;
  2575.        end;
  2576.  
  2577.  
  2578.        {--------------------------------------------------------------}
  2579.        { Main Program }
  2580.  
  2581.        begin
  2582.           Init;
  2583.           DoProgram;
  2584.        end.
  2585.        {--------------------------------------------------------------}
  2586.  
  2587.  
  2588.        Compare this program with its  single-character  counterpart.   I
  2589.        think you will agree that the differences are minor.
  2590.  
  2591.  
  2592.        CONCLUSION
  2593.  
  2594.        At this point, you have learned how to parse  and  generate  code
  2595.        for expressions,  Boolean  expressions,  and  control structures.
  2596.        You have now learned how to develop lexical scanners, and  how to
  2597.        incorporate their elements into a translator.  You have still not
  2598.        seen ALL the elements combined into one program, but on the basis
  2599.        of  what  we've  done before you should find it a straightforward
  2600.        matter to extend our earlier programs to include scanners.
  2601.  
  2602.        We are very  close  to  having  all  the elements that we need to
  2603.        build a real, functional compiler.  There are still a  few things
  2604.        missing, notably procedure  calls  and type definitions.  We will
  2605.        deal with  those  in  the  next  few  sessions.  Before doing so,
  2606.        however, I thought it  would  be fun to turn the translator above
  2607.        into a true compiler.  That's what we'll  be  doing  in  the next
  2608.        installment.
  2609.  
  2610.        Up till now, we've taken  a rather bottom-up approach to parsing,
  2611.        beginning with low-level constructs and working our way  up.   In
  2612.        the next installment,  I'll  also  be  taking a look from the top
  2613.        down,  and  we'll  discuss how the structure of the translator is
  2614.        altered by changes in the language definition.A6A6
  2615.                                     - 43 -A*A*
  2616.  
  2617. PA A
  2618.  
  2619.  
  2620.  
  2621.  
  2622.  
  2623.        See you then.
  2624.  
  2625.        *****************************************************************
  2626.        *                                                               *
  2627.        *                        COPYRIGHT NOTICE                       *
  2628.        *                                                               *
  2629.        *   Copyright (C) 1988 Jack W. Crenshaw. All rights reserved.   *
  2630.        *                                                               *
  2631.        *****************************************************************AUAU
  2632.  
  2633.  
  2634.  
  2635.  
  2636.  
  2637. AHAH
  2638.                                     - 44 -A*A*
  2639. @